<script setup>
// 自定义说明
//  上方添加功能区
//  快速配置列：新增input和checkbox
//  表格统一样式；可以拖动，行和列
import MetaIcon from "@/components/MetaIcon.vue"
import {Sortable} from 'sortablejs'
import {nanoid} from "nanoid"
import {camel, snake, pascal, colorMapping} from "@/utils/tool.js"
import MetaMain from "@/components/MetaMain.vue";
import {ElMessageBox} from "element-plus";

// 操作提示
const help = ref(`
<b>方向键</b>
<div>1. ↑↓：上下</div>
<div>2. ↑↓ + Alt：移动选中行</div>
<div>3. ↑↓ + Alt + Ctrl：移动选中行到顶或低</div>
<div></div>
<b>Enter</b>
<div><b>1. Enter：默认同↓，移动到下一行</b></div>
<div><b>2. Enter + Ctrl：下方新增一行</b></div>
<div>3. Enter + Shift：上方新增一行</div>
`)

// 表格数据和动态列
const {data, cols, colorProperty, allowAddRow, showCode} = defineProps({
  data: {type: Array, default: []},
  cols: {type: Array, default: []},

  colorProperty: {type: String, default: 'code'},
  showHeader : {type: Boolean, default: false},
  showMoveBtn: {type: Boolean, default: false},
  showToolBtn: {type: Boolean, default: false},
  allowAddRow : {type: Boolean, default: false},
  showCode : {type: Boolean, default: false},
})

// region 行列可以动态拖拽 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 参考: https://blog.csdn.net/qq_52845451/article/details/134205694
// https://sortablejs.com/options
const table = useTemplateRef('table')
function rowDrag() {
  Sortable.create(
      table.value.$el.querySelector('.el-table__body-wrapper tbody'),
      {
        // handle：selector 格式为简单css选择器的字符串，使列表单元中符合选择器的元素成为拖动的手柄，只有按住拖动手柄才能使列表单元进行拖动；
        handle: '.drag-handle',
        onEnd: ({oldIndex, newIndex}) => {
          const dragRow = data.splice(oldIndex, 1)[0]
          data.splice(newIndex, 0, dragRow)
        }
      }
  )
}
function colDrag() {
  Sortable.create(
      table.value.$el.querySelector('.el-table__header-wrapper tr'),
      {
        // filter：selector 格式为简单css选择器的字符串，定义哪些列表单元不能进行拖放，可设置为多个选择器，中间用“，”分隔；
        filter: '.drag-ignore',
        onEnd: ({oldIndex, newIndex}) => {
          // 第一列是序号
          const dragCol = cols.splice(oldIndex - 1, 1)[0]
          cols.splice(newIndex - 1, 0, dragCol)
        }
      }
  )
}

// 多行同时拖曳暂时有点问题，先不处理
// function multiRowDrag() {
//   Sortable.mount(new MultiDrag());
//   Sortable.create(
//       table.value.$el.querySelector('.el-table__body-wrapper tbody'),
//       {
//         multiDrag: true,
//         selectedClass: 'selected'
//       }
//   )
// }
onMounted(() => {
  rowDrag()
  colDrag()
})
// endregion

// region 选中行 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const select = reactive({
  rows: [],
  colProperties: [],
  focusRow: null,
  focusCell: [-1, -1]
})

// 表头样式: 是否选中
function headerCellClassName({row, column}) {
  return select.rows.map(row => row.id).includes(row.id) || select.colProperties.includes(column.property) ? 'selected' : null
}

// 单元格样式: 是否选中
function cellClassName({row, column, rowIndex, columnIndex}) {
  return select.rows.map(row => row.id).includes(row.id) || select.colProperties.includes(column.property) ? 'selected' : null
}

// 单元格样式: 颜色显示
function cellStyle({row, column, rowIndex, columnIndex}) {
  if (row.color) return {color: row.color} // 优先考虑列中定义的颜色
  const color = colorMapping(row[colorProperty])
  return {color}
}

function resetRow(){
  select.rows = []
}
function resetCol(){
  select.colProperties = []
}
function getIndex(arr, property, propertyValue) {
  for (let i = 0; i < arr.length; i++) {
    if (arr[i][property] === propertyValue) return i
  }
  return -1
}

function getRowIndexes() {
  const rows = select.rows.length > 0 ? select.rows : [select.focusRow]
  return rows.map(row => row.id).map(id => getIndex(data, 'id', id)).sort((a, b) => a - b)
}
function getColIndexes() {
  return select.colProperties.map(property => getIndex(table.value.columns, 'property', property)).sort((a, b) => a - b)
}

function selectRow(row) {
  if (!select.rows.map(row => row.id).includes(row.id)) {
    select.rows.push(row)
  }
}

function selectCol(column) {
  if (!select.colProperties.includes(column.property)) {
    select.colProperties.push(column.property)
  }
}

// 重置选择
function resetSelected() {
  resetRow()
  resetCol()
}

// 选择行: 可以按Ctrl选择多行，可以按Shift选择连续的多行
function cellClick(row, column, cell, event) {
  if (column.no !== 0) return // 点击第一列#才可以选中
  event.stopPropagation()
  resetCol()

  if (event.shiftKey) { // 连续多行
    selectRow(row)
    const rowIndexes = getRowIndexes()
    const min = rowIndexes[0]
    const max = rowIndexes[rowIndexes.length - 1]
    for (let i = min + 1; i < max; i++) {
      selectRow(data[i])
    }
  } else if (event.ctrlKey) { // 逐个选中
      selectRow(row)
  } else {
    resetRow()
    selectRow(row)
  }
}

// 选择列
function headerClick(column, event) {
  event.stopPropagation()

  // 首列是序号，不是选中列，而是选中所有行（清空数据等）
  if (column.no === 0) {
    select.rows = data
    return
  }

  resetRow()

  if (event.shiftKey) {
    selectCol(column)
    const colIndexes = getColIndexes()
    const min = colIndexes[0]
    const max = colIndexes[colIndexes.length - 1]
    for (let i = min + 1; i < max; i++) {
      selectCol(table.value.columns[i])
    }
  } else if (event.ctrlKey) {
    selectCol(column)
  } else {
    resetCol()
    selectCol(column)
  }
}
// endregion

// region 表格快捷键: Delete删除行，↑↓ 光标上下，Alt + ↑↓移动行， + Ctrl则为顶部底部
function tableKeyDown(event) {
  event.stopPropagation()
  event.preventDefault()

  if (event.key === 'Delete') { // 删除选中行
    delRow()
  } else if (event.ctrlKey && event.altKey && event.key === 'ArrowUp'  ) { // 移动行到顶部
    toTop()
  } else if (event.ctrlKey && event.altKey && event.key === 'ArrowDown') { // 移动行到底部
    toBottom()
  } else if (event.altKey && event.key === 'ArrowUp'  ) { // 移动行向上
    toUp()
  } else if (event.altKey && event.key === 'ArrowDown') { // 移动行向下
    toDown()
  } else if (event.ctrlKey && event.key === 'ArrowUp'  ) { // 焦点到顶部
    rowCellFocusMove(0)
  } else if (event.ctrlKey && event.key === 'ArrowDown') { // 焦点到底部
    rowCellFocusMove(data.length - 1)
  } else if (event.key === 'ArrowUp'  ) { // 焦点向上
    rowCellFocusMove(null,-1)
  } else if (event.key === 'ArrowDown') { // 焦点向下
    rowCellFocusMove(null,1)
  }
}

// 焦点移动
function rowCellFocusMove(rowIndex, rowOffset) {
  // 当前没有焦点或焦点不是表内的单元格则不处理
  const activeCell = document.activeElement
  if (!activeCell) return
  if (!activeCell.id?.startsWith('cell-')) return

  // cell-5-6
  const arr = activeCell.id.split('-')
  if (arr.length !== 3) return

  let cellRow = parseInt(arr[1])
  let cellCol = parseInt(arr[2])

  if (rowIndex != null) { // 顶部底部
    cellRow = rowIndex
  } else { // 相对上下
    cellRow = cellRow + rowOffset
  }

  focusCell(cellRow, cellCol)
}
// endregion

// region 输入框回车 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 默认：自动跳到下一行，最后一行则自动新增一行;
// 扩展：如果按下Ctrl键则自动新增空行（下）, 按下Shift则自动新增空行（上）
function inputEnter(scope, event) {
  let focusId = `#cell-${scope.$index + 1}-${scope.cellIndex}`
  const focusCell = table.value.$el.querySelector(focusId)

  if (event.shiftKey) { // 上方新增一行，并聚焦
    focusId = `#cell-${scope.$index}-${scope.cellIndex}`
    data.splice(scope.$index, 0, newRow())
  } else if (event.ctrlKey) {  // 按下Ctrl则当前行的下一行新增一行
    data.splice(scope.$index + 1, 0, newRow())
  } else {
    if (focusCell) {
      focusCell.focus()
    } else {
      if (allowAddRow) {
        data.push(newRow()) // 末尾新增一行, 并再次尝试聚焦
      }
    }
  }

  nextTick(() => {
    const focusInput = table.value.$el.querySelector(focusId)
    if (focusInput) {
      focusInput.focus()
    }
  })
}

function inputFocus(scope, event) {
  select.focusRow  = scope.row
  select.focusCell = [scope.$index, scope.column.no]
}

// TODO 失去焦点就不能点击按钮了
function inputBlur(scope, event) {
  // select.focusRow = null
  // select.focusCell = [-1, -1]
}
// endregion

// region 移动增删按钮组 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const moveBtnDisabled = computed(() => (select.rows.length === 0 || select.rows.length === data.length) && select.focusRow == null)
const delBtnDisabled  = computed(() => select.rows.length === 0)

function toTop() {
  if (moveBtnDisabled.value) return
  const ids = select.rows.length > 0 ? select.rows.map(row => row.id) : [select.focusRow.id]
  const tars = data.filter(row => ids.includes(row.id))
  const rems = data.filter(row => !ids.includes(row.id))
  data.splice(0, data.length)
  data.push(...tars)
  data.push(...rems)
  focusCellAfterMove(0)
}

function toBottom() {
  if (moveBtnDisabled.value) return
  const ids = select.rows.length > 0 ? select.rows.map(row => row.id) : [select.focusRow.id]
  const tars = data.filter(row => ids.includes(row.id))
  const rems = data.filter(row => !ids.includes(row.id))
  data.splice(0, data.length)
  data.push(...rems)
  data.push(...tars)
  focusCellAfterMove(data.length - 1)
}

function toUp() {
  if (moveBtnDisabled.value) return
  const indexes = getRowIndexes()
  if (indexes.includes(0)) return  // 到达顶部
  for (let i = 0; i < indexes.length; i++) {
    const rowIndex = indexes[i]
    const row = data.splice(rowIndex, 1)[0]
    data.splice(rowIndex - 1, 0, row)
  }
  focusCellAfterMove(null, -1)
}

function toDown() {
  if (moveBtnDisabled.value) return
  const indexes = getRowIndexes().reverse()
  if (indexes.includes(data.length - 1)) return  // 到达底部
  for (let i = 0; i < indexes.length; i++) {
    const rowIndex = indexes[i]
    const row = data.splice(rowIndex, 1)[0]
    data.splice(rowIndex + 1, 0, row)
  }
  focusCellAfterMove(null, 1)
}

// 单元格聚焦
function focusCell(rowIndex, colIndex) {
  if (rowIndex < 0 || rowIndex > data.length - 1) return
  nextTick(() => {
    const focusId = `#cell-${rowIndex}-${colIndex}`
    const focusCell = table.value.$el.querySelector(focusId)
    if (focusCell) {
      focusCell.blur()  // 向下移动时焦点保留了，不会再次触发focus事件，因此先让其失去焦点以下
      focusCell.focus()
    }
  })
}

// 移动后单元格焦点保持
function focusCellAfterMove(rowIndex, rowOffset) {
  if (select.rows.length === 0) {
    if (rowIndex == null) {
      focusCell(select.focusCell[0] + rowOffset, select.focusCell[1])
    } else {
      focusCell(rowIndex, select.focusCell[1])
    }
  }
}

function delRow() {
  //const ids = select.rows.length > 0 ? select.rows.map(row => row.id) : [select.focusRow.id]
  const ids = select.rows.map(row => row.id)
  ElMessageBox.confirm(`确定要删除${ids.length}条记录吗？`, '提示',{})
      .then(() => {
        const rems = data.filter(row => !ids.includes(row.id))
        data.splice(0, data.length)
        data.push(...rems)
      })
      .catch(() => {
      })
}

function addRow(count = 1) {
  if (!allowAddRow) return // 不允许新增则返回

  if (select.focusRow == null) {
    for (let i = 0; i < count; i++) {
      data.push(newRow())
    }
  } else {
    for (let i = 0; i < count; i++) {
      data.splice(select.focusCell[0] + 1, 0, newRow())
    }
  }
}

// 新的一行数据
function newRow(){
  return {id: nanoid(), columnType: 'varchar(256)'}
}

// endregion

// region 大小写驼峰下划线
const toUpper = ref(true)
const toolBtnDisabled = computed(() => select.rows.length === 0 && select.colProperties.length === 0)
function tool(command) {
  if (toolBtnDisabled.value) return

  if ('upper_lower' === command) {
    command = toUpper.value ? 'upper' : 'lower'
    toUpper.value = !toUpper.value
  }

  if (select.rows.length > 0) {
    select.rows.forEach(row => {
      cols.forEach(item => {
        if (item.type === 'input') {
          row[item.prop] = transfer(row[item.prop], command)
        }
      })
    })
  } else {
    data.forEach(row => {
      cols.forEach(item => {
        if (item.type === 'input' && select.colProperties.includes(item.prop)) {
          row[item.prop] = transfer(row[item.prop], command)
        }
      })
    })
  }
}

function transfer(value, command) {
  if (command === 'upper') return value?.toUpperCase()
  if (command === 'lower') return value?.toLowerCase()
  if (command === 'camel') return camel(value)
  if (command === 'snake') return snake(value)
  return value
}

// endregion

// region 模板编辑
const code = reactive({
  visible: false,
  title: '',
  fullscreen: false,
  sample: '',
  row: '',
  prop: '',
})
function editCode(row, prop) {
  code.title = '【' + (row.code || row.name) + '】【' + pascal(prop) + '】'
  code.visible = true
  code.row = row
  code.prop = prop
}

function fullscreen() {
  code.fullscreen = !code.fullscreen
}
// endregion

function openLink(link) {
  window.open(link, '_blank')
}
</script>

<template>
  <div class="container">
    <div class="header" v-if="showHeader">
      <div class="left" v-if="showMoveBtn">
        <el-button-group>
          <el-button @click="toTop"    :disabled="moveBtnDisabled"><SvgIcon name="top" style="margin-right: 2px"/>顶</el-button>
          <el-button @click="toUp"     :disabled="moveBtnDisabled" icon="el-icon-top">上</el-button>
          <el-button @click="toDown"   :disabled="moveBtnDisabled" icon="el-icon-bottom">下</el-button>
          <el-button @click="toBottom" :disabled="moveBtnDisabled" icon="el-icon-download">底</el-button>
          <el-button @click="delRow"   :disabled="delBtnDisabled"  icon="el-icon-minus">删</el-button>
          <el-dropdown placement="top-start" split-button plain @click="addRow(1)" @command="addRow">
            <el-icon style="margin-right: 2px"><el-icon-plus/></el-icon>增
            <template #dropdown>
              <el-dropdown-menu >
                <el-dropdown-item command="5">新增5行</el-dropdown-item>
                <el-dropdown-item command="10">新增10行</el-dropdown-item>
                <el-dropdown-item command="15">新增15行</el-dropdown-item>
              </el-dropdown-menu>
            </template>
          </el-dropdown>
        </el-button-group>

        <el-button-group style="margin-left: 20px" v-if="showToolBtn">
          <el-tooltip :content="help" raw-content effect="light">
            <el-button icon="el-icon-question-filled"></el-button>
          </el-tooltip>
          <el-dropdown placement="top-start" split-button plain @click="tool('upper_lower')" @command="tool" :disabled="toolBtnDisabled">
            <MetaIcon name="" icon="aa"/>
            <template #dropdown>
              <el-dropdown-menu>
                <el-dropdown-item command="snake"><MetaIcon name="下划线" icon="snake"/></el-dropdown-item>
                <el-dropdown-item command="camel"><MetaIcon name="转驼峰" icon="camel"/></el-dropdown-item>
                <el-dropdown-item command="english" disabled><MetaIcon name="转英文" icon="english"/></el-dropdown-item>
                <el-dropdown-item command="pinyin"  disabled><MetaIcon name="转拼音" icon="pinyin"/></el-dropdown-item>
              </el-dropdown-menu>
            </template>
          </el-dropdown>
        </el-button-group>
      </div>

      <div class="right">
        <!-- 右边按钮自定义 -->
        <slot></slot>
      </div>
    </div>
    <div class="main">
      <el-table ref="table" :data="data" size="small" border row-key="id"
                :cell-style="cellStyle"
                :cell-class-name="cellClassName" :header-cell-class-name="headerCellClassName"
                @click="resetSelected" @keyup="tableKeyDown"
                @header-click="headerClick" @cell-click="cellClick"
      >
        <el-table-column label="#" width="40" align="center" label-class-name="drag-ignore" class-name="drag-handle">
          <template #default="scope">
            <div class="index">{{scope.$index + 1}}</div>
          </template>
        </el-table-column>

        <!-- 动态列 -->
        <template v-for="item in cols" :key="item.id">
          <el-table-column v-if="item.type" v-bind="item">
            <template #header v-if="item.icon">
              <MetaIcon :name="item.icon" />
            </template>
            <template #default="scope">
                <el-input v-if="item.type === 'input'" v-model="scope.row[item.prop]" v-bind="item.input"
                          @keydown.enter="inputEnter(scope, $event)"
                          @focus="inputFocus(scope, $event)"
                          @blur="inputBlur(scope, $event)"
                          :id="`cell-${scope.$index}-${scope.cellIndex}`">
                </el-input>
                <el-checkbox     v-else-if="item.type === 'checkbox'" v-model="scope.row[item.prop]" :id="`cell-${scope.$index}-${scope.cellIndex}`"></el-checkbox>
                <el-switch       v-else-if="item.type === 'switch'"   v-model="scope.row[item.prop]" style="--el-switch-on-color: currentColor;" :id="`cell-${scope.$index}-${scope.cellIndex}`"></el-switch>
                <el-color-picker v-else-if="item.type === 'color'"    v-model="scope.row[item.prop]" :id="`cell-${scope.$index}-${scope.cellIndex}`"></el-color-picker>
                <meta-icon       v-else-if="item.type === 'icon'"     :name="scope.row[item.prop]" :id="`cell-${scope.$index}-${scope.cellIndex}`" style="margin-left: 8px"></meta-icon>
                <el-link         v-else-if="item.type === 'link'"     type="primary" :underline="false" :id="`cell-${scope.$index}-${scope.cellIndex}`" @click="openLink(scope.row['link'])">
                  <span style="margin: 0 5px 0 8px">{{scope.row[item.prop]}}</span>
                  <svg-icon name="link"></svg-icon>
                </el-link>
                <div class="code-cell" v-else-if="showCode && item.type === 'code'" :id="`cell-${scope.$index}-${scope.cellIndex}`" @click="editCode(scope.row, item.prop)">
                  <div>{{scope.row[item.prop]?.length > 0 ? scope.row[item.prop]?.length : ''}}</div>
                  <el-icon style="font-size: 20px"><el-icon-edit/></el-icon>
                </div>
                <div v-else :id="`cell-${scope.$index}-${scope.cellIndex}`">{{scope.row[item.prop]}}</div>
            </template>
          </el-table-column>
          <el-table-column v-else v-bind="item"></el-table-column>
        </template>
      </el-table>
    </div>
  </div>

  <el-dialog v-if="showCode" v-model="code.visible" width="80vw"
             draggable overflow destroy-on-close align-center
             :fullscreen="code.fullscreen">
    <template #header>
      <div class="el-dialog__title">代码编辑 {{ code.title }}</div>
      <el-button class="fullscreen" plain icon="el-icon-full-screen" @click="fullscreen"></el-button>
    </template>
    <MetaMain v-model="code.row[code.prop]" :language="code.prop" :style="{height: code.fullscreen ? '' : '80vh'}"/>
  </el-dialog>
</template>

<style scoped lang="scss">
.container {
  --el-font-size-extra-small: 14px; // 表格标题文字
  --el-font-size-base: 14px;        // 表格内部文字

  :deep(.header) {
    padding: 0 0 10px 0;

    display: flex;
    justify-content: space-between;

    .el-button {
      padding: 5px;
      //border-radius: 0;
    }

    .el-button [class*=el-icon]+span {
      margin-left: 2px;
    }

    // 最后的新增可以是下拉框，圆角处理
    .el-dropdown .el-button-group>.el-button:first-child {
      border-radius: 0;
    }
  }

  :deep(.main) {
    // 拖曳光标
    .drag-handle {
      cursor: pointer;
    }
    .sortable-ghost {
      background-color: var(--el-color-primary-light-8);
    }

    // 选中行
    .selected {
      background-color: var(--el-color-primary-light-8);
    }

    tbody {
      // 上下
      .el-table__cell {
        padding: 0;
      }

      // 左右
      .cell {
        padding: 0;
      }

      // 表格内的输入框的边框
      .el-input {
        --el-input-bg-color: transparent;
      }

      .el-input__wrapper {
        border-radius: 0;
        box-shadow: none;
      }

      .el-input__wrapper:hover {
        box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset
      }

      .el-input__wrapper.is-focus {
        box-shadow: 0 0 0 1px var(--el-input-focus-border-color, var(--el-border-color)) inset
      }

      // 输入框的颜色
      .el-input__inner {
        color: unset;
        //-webkit-text-fill-color: unset; // disabled的情况下也显示颜色
      }

      // 按照当前行的颜色显示
      .el-link {
        color: unset;

        &:hover {
          color: var(--el-link-hover-text-color);
        }
      }
    }
  }

  .code-cell {
    display: flex;
    justify-content: space-between;
    padding: 0 10px;
  }
}

.fullscreen {
  position: absolute;
  top: 0;
  right: 48px;
  height: 46px;
  width: 48px;
  border: none;
}
</style>